using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Colman.Rpg.Properties;

namespace Colman.Rpg.Controls
{
    /// <summary>
    /// The type of battle being fought.
    /// </summary>
    public enum BattleType
    {
        /// <summary>
        /// NPC fight - against a monster.
        /// </summary>
        Npc,
        /// <summary>
        /// PVP fight - against a player.
        /// </summary>
        Pvp
    }

    /// <summary>
    /// The control showing the battleground with two characters.
    /// </summary>
    public partial class BattleGroundControl : Control
    {
        private enum AnimationStage
        {
            Nothing = 0,
            Approached = 1,
            Defenses = 2,
            Attacks = 3,
        }

        private struct Tuple<T>
        {
            public Tuple(T left, T right)
            {
                this.Left = left;
                this.Right = right;
            }

            public T Left, Right;
        }

        private AnimationStage animationStage = AnimationStage.Nothing;
        private Timer animationTimer;
        private int tick = 0;
        private int leftDefendPoints, rightDefendPoints, leftAttackPoints, rightAttackPoints;

        /// <summary>
        /// Creates a new instance of <see cref="BattleGroundControl" />.
        /// </summary>
        public BattleGroundControl()
        {
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.FixedHeight, true);
            this.SetStyle(ControlStyles.FixedWidth, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.ResizeRedraw, false);
            this.SetStyle(ControlStyles.Selectable, false);
            this.SetStyle(ControlStyles.SupportsTransparentBackColor, false);
            this.SetStyle(ControlStyles.UserPaint, true);

            this.animationTimer = new Timer();
            this.animationTimer.Enabled = false;
            this.animationTimer.Interval = 1000;

            this.animationTimer.Tick += new EventHandler(animationTimer_Tick);

            InitializeComponent();
        }

        #region BattleType BattleType { get; set; } (battleType)
        private BattleType battleType;

        /// <summary>
        /// Gets or sets the type of battle being fought.
        /// </summary>
        [Description("Indicates the type of battle being fought.")]
        public BattleType BattleType
        {
            [System.Diagnostics.DebuggerStepThrough]
            get
            {
                return this.battleType;
            }
            [System.Diagnostics.DebuggerStepThrough]
            set
            {
                if (this.battleType != value)
                {
                    this.battleType = value;

                    this.Invalidate();
                }
            }
        }
        #endregion

        #region int AnimationStageTime { get; set; }
        /// <summary>
        /// Gets or sets the time an animation stage takes in milliseconds.
        /// </summary>
        [DefaultValue(1000)]
        [Description("Indicates the time an animation stage takes in milliseconds.")]
        public int AnimationStageTime
        {
            [System.Diagnostics.DebuggerStepThrough]
            get
            {
                return this.animationTimer.Interval;
            }
            [System.Diagnostics.DebuggerStepThrough]
            set
            {
                if (this.animationTimer.Interval != value)
                {
                    this.animationTimer.Interval = value;
                }
            }
        }
        #endregion

        #region int AnimationSmoothness { get; set; } (animationSmoothness)
        private int animationSmoothness = 15;

        /// <summary>
        /// Gets or sets the smoothness of the animation. Higher values = smoother, but slower.
        /// </summary>
        [DefaultValue(15)]
        [Description("Indicates he smoothness of the animation. Higher values = smoother, but slower.")]
        public int AnimationSmoothness
        {
            [System.Diagnostics.DebuggerStepThrough]
            get
            {
                return this.animationSmoothness;
            }
            [System.Diagnostics.DebuggerStepThrough]
            set
            {
                if (this.animationSmoothness <= 0)
                    throw new ArgumentOutOfRangeException("Must be a positive value.");

                if (this.animationSmoothness != value)
                {
                    this.animationSmoothness = value;
                }
            }
        }
        #endregion

        /// <summary>
        /// Performs the work of setting the specified bounds of this control.
        /// </summary>
        /// <param name="x">The new <see cref="Control.Left"/> property value of the control.</param>
        /// <param name="y">The new <see cref="Control.Top"/> property value of the control.</param>
        /// <param name="width">The new <see cref="Control.Width"/> property value of the control.</param>
        /// <param name="height">The new <see cref="Control.Height"/> property value of the control.</param>
        /// <param name="specified">A bitwise combination of the <see cref="BoundsSpecified"/> values.</param>
        protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
        {
            width = Resources.BattleField.Width;
            height = Resources.BattleField.Height;

            base.SetBoundsCore(x, y, width, height, specified);
        }

        #region | Custom Drawing |
        private const int PaddingDistance = 25;
        
        private Point leftStandPosition = new Point(PaddingDistance - 1, (Resources.BattleField.Height - Resources.HeroLeftStand.Height) / 3 * 2);
        private Point rightStandPosition = new Point(Resources.BattleField.Width - Resources.HeroRightStand.Width - PaddingDistance, (Resources.BattleField.Height - Resources.HeroRightStand.Height) / 3 * 2);

        private Point leftApproachPosition = new Point(Resources.BattleField.Width / 2 - Resources.HeroLeftStand.Width, (Resources.BattleField.Height - Resources.HeroLeftStand.Height) / 3 * 2);
        private Point rightApproachPosition = new Point(Resources.BattleField.Width / 2, (Resources.BattleField.Height - Resources.HeroRightStand.Height) / 3 * 2);

        private Tuple<PointF> GetCurrentApproachPositions()
        {
            PointF left = new PointF((float)(leftStandPosition.X * (this.animationSmoothness - this.tick) + leftApproachPosition.X * this.tick) / this.animationSmoothness,
                leftStandPosition.Y);
            PointF right = new PointF((float)(rightStandPosition.X * (this.animationSmoothness - this.tick) + rightApproachPosition.X * this.tick) / this.animationSmoothness,
                rightStandPosition.Y);

            return new Tuple<PointF>(left, right);
        }

        /// <summary>
        /// Paints the control.
        /// </summary>
        /// <param name="e">A <see cref="PaintEventArgs"/> that contains the event data.</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            switch (this.animationStage)
            {
                case AnimationStage.Nothing:
                    // Simple drawing - nothing's happening.
                    e.Graphics.DrawImage(Resources.HeroLeftStand, new Rectangle(leftStandPosition, Resources.HeroLeftStand.Size));

                    if (this.battleType == BattleType.Npc)
                        e.Graphics.DrawImage(Resources.MonsterStand, new Rectangle(rightStandPosition, Resources.HeroLeftStand.Size));
                    else
                        e.Graphics.DrawImage(Resources.HeroRightStand, new Rectangle(rightStandPosition, Resources.HeroLeftStand.Size));
                    break;
                case AnimationStage.Approached:
                    // They're going against one another (round animation start).
                    Tuple<PointF> points = this.GetCurrentApproachPositions();

                    e.Graphics.DrawImage(Resources.HeroLeftStand, new Rectangle(Point.Ceiling(points.Left), Resources.HeroLeftStand.Size));

                    if (this.battleType == BattleType.Npc)
                        e.Graphics.DrawImage(Resources.MonsterStand, new Rectangle(Point.Truncate(points.Right), Resources.HeroLeftStand.Size));
                    else
                        e.Graphics.DrawImage(Resources.HeroRightStand, new Rectangle(Point.Truncate(points.Right), Resources.HeroLeftStand.Size));
                    break;
                case AnimationStage.Defenses:
                    // Either of them is defending.
                    e.Graphics.DrawImage(Resources.HeroLeftStand, new Rectangle(leftApproachPosition, Resources.HeroLeftStand.Size));

                    if (this.battleType == BattleType.Npc)
                        e.Graphics.DrawImage(Resources.MonsterStand, new Rectangle(rightApproachPosition, Resources.HeroLeftStand.Size));
                    else
                        e.Graphics.DrawImage(Resources.HeroRightStand, new Rectangle(rightApproachPosition, Resources.HeroLeftStand.Size));

                    if (this.leftDefendPoints > 0)
                    {
                        // Draw points gained
                        SizeF textSize = e.Graphics.MeasureString(this.leftDefendPoints.ToString(), this.Font);

                        PointF drawLocation = new PointF(
                            leftApproachPosition.X + Resources.HeroLeftStand.Width / 2 - textSize.Width / 2,
                            leftApproachPosition.Y - PaddingDistance - textSize.Height);

                        e.Graphics.DrawString(this.leftDefendPoints.ToString(), this.Font, Brushes.Green, drawLocation);
                    }

                    if (this.rightDefendPoints > 0)
                    {
                        // Draw points gained
                        SizeF textSize = e.Graphics.MeasureString(this.rightDefendPoints.ToString(), this.Font);

                        PointF drawLocation = new PointF(
                            rightApproachPosition.X + Resources.HeroLeftStand.Width / 2 - textSize.Width / 2,
                            rightApproachPosition.Y - PaddingDistance - textSize.Height);

                        e.Graphics.DrawString(this.rightDefendPoints.ToString(), this.Font, Brushes.Green, drawLocation);
                    }

                    break;
                case AnimationStage.Attacks:
                    // Either of them is attacking.
                    if (this.leftAttackPoints > 0)
                    {
                        e.Graphics.DrawImage(Resources.HeroLeftAttack, new Rectangle(leftApproachPosition, Resources.HeroLeftStand.Size));

                        // Draw points lost
                        SizeF textSize = e.Graphics.MeasureString(this.leftAttackPoints.ToString(), this.Font);

                        PointF drawLocation = new PointF(
                            rightApproachPosition.X + Resources.HeroLeftStand.Width / 2 - textSize.Width / 2,
                            rightApproachPosition.Y - PaddingDistance - textSize.Height);

                        e.Graphics.DrawString(this.leftAttackPoints.ToString(), this.Font, Brushes.Red, drawLocation);
                    }
                    else
                    {
                        e.Graphics.DrawImage(Resources.HeroLeftStand, new Rectangle(leftApproachPosition, Resources.HeroLeftStand.Size));
                    }

                    if (this.rightAttackPoints > 0)
                    {
                        if (this.battleType == BattleType.Npc)
                            e.Graphics.DrawImage(Resources.MonsterAttack, new Rectangle(rightApproachPosition, Resources.HeroLeftStand.Size));
                        else
                            e.Graphics.DrawImage(Resources.HeroRightAttack, new Rectangle(rightApproachPosition, Resources.HeroLeftStand.Size));

                        // Draw points lost
                        SizeF textSize = e.Graphics.MeasureString(this.rightAttackPoints.ToString(), this.Font);

                        PointF drawLocation = new PointF(
                            leftApproachPosition.X + Resources.HeroLeftStand.Width / 2 - textSize.Width / 2,
                            leftApproachPosition.Y - PaddingDistance - textSize.Height);

                        e.Graphics.DrawString(this.rightAttackPoints.ToString(), this.Font, Brushes.Red, drawLocation);
                    }
                    else
                    {
                        if (this.battleType == BattleType.Npc)
                            e.Graphics.DrawImage(Resources.MonsterStand, new Rectangle(rightApproachPosition, Resources.HeroLeftStand.Size));
                        else
                            e.Graphics.DrawImage(Resources.HeroRightStand, new Rectangle(rightApproachPosition, Resources.HeroLeftStand.Size));
                    }
                    break;
            }
        }

        /// <summary>
        /// Animates a round.
        /// </summary>
        /// <param name="leftDefendPoints">The number of points gained by defneding for the left character.</param>
        /// <param name="rightDefendPoints">The number of points gained by defneding for the right character.</param>
        /// <param name="leftAttackPoints">The number of points hit by attacking for the left character.</param>
        /// <param name="rightAttackPoints">The number of points hit by attacking for the right character.</param>
        public void Animate(int leftDefendPoints, int rightDefendPoints, int leftAttackPoints, int rightAttackPoints)
        {
            if (this.animationStage != AnimationStage.Nothing)
                throw new InvalidOperationException("Can not animate twice at the same time. Use the AnimationComplete event to determine when animation completes and then call this method.");

            this.leftDefendPoints = leftDefendPoints;
            this.rightDefendPoints = rightDefendPoints;
            this.leftAttackPoints = leftAttackPoints;
            this.rightAttackPoints = rightAttackPoints;

            this.animationTimer.Enabled = true;
        }

        /// <summary>
        /// Paints the background of the control.
        /// </summary>
        /// <param name="e">A <see cref="PaintEventArgs"/> that contains the event data.</param>
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            base.OnPaintBackground(e);

            e.Graphics.DrawImageUnscaled(Resources.BattleField, 0, 0);
        }

        private void animationTimer_Tick(object sender, EventArgs e)
        {
            switch (this.animationStage)
            {
                case AnimationStage.Nothing:
                    this.animationStage = AnimationStage.Approached;

                    this.tick = 0;
                    this.animationTimer.Interval = this.animationTimer.Interval / this.animationSmoothness;
                    this.Invalidate();
                    break;
                case AnimationStage.Approached:
                    if (this.tick == this.animationSmoothness)
                    {
                        this.animationTimer.Interval = this.animationTimer.Interval * this.animationSmoothness;

                        this.animationStage = AnimationStage.Defenses;

                        // No one defended? Skip it.
                        if (this.leftDefendPoints == 0 && this.rightDefendPoints == 0)
                            this.animationTimer_Tick(sender, e);

                        this.Invalidate();
                    }
                    else
                    {
                        Tuple<PointF> beforeF = this.GetCurrentApproachPositions();
                        Tuple<Point> before = new Tuple<Point>(Point.Truncate(beforeF.Left), Point.Ceiling(beforeF.Right));

                        this.tick++;

                        Tuple<PointF> afterF = this.GetCurrentApproachPositions();
                        Tuple<Point> after = new Tuple<Point>(Point.Ceiling(afterF.Left), Point.Truncate(afterF.Right));

                        Rectangle leftRect = new Rectangle(before.Left.X, before.Left.Y, after.Left.X + Resources.HeroLeftStand.Width - before.Left.X, Resources.HeroLeftStand.Height);
                        Rectangle rightRect = new Rectangle(after.Right.X, after.Right.Y, before.Right.X + Resources.HeroLeftStand.Width - after.Right.X, Resources.HeroLeftStand.Height);

                        this.Invalidate(leftRect);
                        this.Invalidate(rightRect);
                        //this.Invalidate();
                    }
                    break;
                case AnimationStage.Defenses:
                    this.animationStage = AnimationStage.Attacks;

                    // No one attacked? Skip it.
                    if (this.leftAttackPoints == 0 && this.rightAttackPoints == 0)
                        this.animationTimer_Tick(sender, e);

                    this.Invalidate();
                    break;
                case AnimationStage.Attacks:
                    this.animationStage = AnimationStage.Nothing;
                    this.animationTimer.Enabled = false;
                    this.Invalidate();
                    this.OnAnimationComplete();
                    break;
            }
        }
        #endregion

        #region event EventHandler AnimationComplete
        /// <summary>
        /// Invokes the <see cref="AnimationComplete" /> event.
        /// </summary>
        [System.Diagnostics.DebuggerStepThrough]
        protected virtual void OnAnimationComplete()
        {
            if (this.AnimationComplete != null)
                this.AnimationComplete(this, EventArgs.Empty);
        }

        /// <summary>
        /// Raised when the animation is complete.
        /// </summary>
        public event EventHandler AnimationComplete;
        #endregion
    }
}
